package interpreter.biback.domain

import biback.utils._
import DomainLogger.DebugArgs
import biback.domain._
import biback.domain.account.{Account, BalanceChangeType, SubAcctNotFound}
import biback.domain.common.model.{BranchLevel, Currency}
import biback.domain.common.BranchAlgebra
import biback.domain.customer.AccountNotFound
import biback.domain.ledger._
import cats.Monad

trait LedgerMock[F[_]] {

  val productFamilies: Map[Name, Option[Name]]
  val productAccountingRules: List[ProductAccounting]
  val peerAccountingRules: List[Accounting]
  val transitRules: Map[BranchLevel, SubjectNo]
  val ledgers: List[Ledger]
  val accounts: List[Account]

  val ledgerIn: LedgerAlgebra.Service[F] = new LedgerAlgebra.Service[F] {
    def findAccounting(productNameOpt: Option[Name], productName: Name): Either[Any, Option[Name]] = {
      for {
        x <- productNameOpt ?= NoAccountingDefinition(productName, "deep")
        p <- productFamilies.get(x) ?= NoProductDefinition(x)
        f  = productAccountingRules.find(a => a.productName == x)
        _ <- Either.cond(f.isEmpty, p, f.orNull)
        x <- findAccounting(p, productName)
      } yield x
    }

    override def getAccounting(productName: Name, trnCode: TrnCode, changeType: BalanceChangeType)(implicit F: Monad[F]): LogErrOrF[FoundAccounting] = {
      val found = findAccounting(Some(productName), productName) match {
        case Left(e: DomainError) => Left(e)
        case Left(a: ProductAccounting) =>
          val peers = peerAccountingRules.filter(x => x.productName == a.productName && x.trnCode == trnCode)
          val direction = if (changeType == BalanceChangeType.`+`) a.direction else a.direction.peer
          orE(peers.nonEmpty, FoundAccounting(a.productName, a.subjectNo, direction, peers), NoAccountingDefinition(productName, s"No peer found for $trnCode"))
        case Left(a: Any) => Left(NoAccountingDefinition(productName, s"Unknown left value: $a"))
        case Right(_) => Left(NoAccountingDefinition(productName, "Unknown right value"))
      }
      iorF(found)
    }

    override def getLedger(ccy: Currency, peers: List[(BranchNo, SubjectNo)])(implicit F: Monad[F]): LogErrOrF[List[Ledger]] = {
      val found = ledgers.filter(p => p.ccy == ccy && peers.contains((p.brcNo, p.subjectNo)))
      iorF(found.length == peers.length, found, LedgerNotFound(ccy, peers), DebugArgs(ccy, peers))
    }

    override def getTransitSubjects()(implicit F: Monad[F]): LogErrOrF[Map[BranchLevel, SubjectNo]] =
      iorF(resultF(transitRules))

    override def getSubAcctInfo(acctId: AccountId, subAcctNo: SubAccountNo)(implicit F: Monad[F]): LogErrOrF[SubAcctInfo] = {
      val found = for {
        a <- accounts.find(_.id == acctId) ?= AccountNotFound(acctId)
        s <- a.subs(subAcctNo) ?= SubAcctNotFound(subAcctNo)
      } yield SubAcctInfo(s.product, s.quotaBrcNo, a.openBrcNo)
      iorF(found)
    }
  }

  val branchIn: BranchAlgebra.Service[F]
  lazy val algebras = new LedgerAlgebra[F] with BranchAlgebra[F] {
    val ledger = ledgerIn
    val branch = branchIn
  }
}

case class NoProductDefinition(name: Name) extends DomainError

case class NoAccountingDefinition(productName: Name, comment: String = "") extends DomainError